﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
using StyleMVVM.View;
using StyleMVVM.View.Impl;
#if NETFX_CORE
using Windows.UI.Xaml;
#else
using System.Windows;
#endif

namespace StyleMVVM.ViewModel.Impl
{
	/// <summary>
	/// This class is responsible or resolving and binding all viewmodels
	/// </summary>
	public sealed class ViewModelResolutionService : IViewModelResolutionService, IDependencyInjectionContainerAware
	{
		private static string supplementalString = "ViewModelResolutionService";
		private IEnumerable<IViewModelBinder> binders;

		/// <summary>
		/// Export this class as an IViewModelResolutionService
		/// </summary>
		/// <param name="container"></param>
		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			container.Register(typeof(ViewModelResolutionService)).As(typeof(IViewModelResolutionService)).
			          ImportMethod("ImportBinders").AndSharedPermenantly();
		}

		/// <summary>
		/// Container to use for resolving models
		/// </summary>
		public IDependencyInjectionContainer Container { get; set; }

		/// <summary>
		/// Import all known model binders, called during activation
		/// </summary>
		/// <param name="binderCollection">all known model binders</param>
		public void ImportBinders(IEnumerable<IViewModelBinder> binderCollection)
		{
			binders = binderCollection;
		}

		/// <summary>
		/// Creates and Binds a viewmodel to a view
		/// </summary>
		/// <param name="view">view to bind to</param>
		/// <param name="viewModelName">name of the view model to bind to</param>
		public bool ResolveViewModel(FrameworkElement view, string viewModelName)
		{
			bool foundModel = false;
			object viewModel = null;
			object oldViewModel = view.DataContext;

			if (!string.IsNullOrEmpty(viewModelName))
			{
				try
				{
					IInjectionContext injectionContext = Container.CreateInjectionContext();

					injectionContext.RegisterExport(typeof(IViewContext), new ViewContext(view));

					viewModel = Container.LocateWithContext(injectionContext, viewModelName);

					if (viewModel != null)
					{
						foundModel = true;

						Logger.Debug(string.Format("Resolved ViewModel Name {0} to Type {1}", viewModelName, viewModel.GetType().FullName));
					}
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while creating ViewModel: " + viewModelName, supplementalString, exp);
				}
			}

			if (foundModel)
			{
				ProcessNewModel(view, viewModel);

				ProcessOldModel(view, oldViewModel);
			}

			return foundModel;
		}

#if DOT_NET

	/// <summary>
	/// Creates and Binds a viewmodel to a view
	/// </summary>
	/// <param name="view">view to bind to</param>
	/// <param name="viewModelName">type of the view model to bind to</param>
		public void ResolveViewModel(FrameworkElement view, Type viewModelType)
		{
			object viewModel = null;
			object oldViewModel = view.DataContext;

			if (viewModelType != null)
			{
				try
				{
					viewModel = Container.LocateByType(viewModelType);

					if (viewModel == null)
					{
						Logger.Error("Could not locate exported ViewModel of type: " + viewModelType.FullName, supplementalString);
					}
					else
					{
						Logger.Debug(string.Format("Resolved ViewModel Type {0} to Type {1}",
						                           viewModelType.FullName,
						                           viewModel.GetType().FullName));
					}
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while creating ViewModel: " + viewModelType.FullName, supplementalString, exp);
				}
			}

			ProcessNewModel(view, viewModel);

			ProcessOldModel(view, oldViewModel);
		}

#endif

		private void ProcessNewModel(FrameworkElement view, object newViewModel)
		{
			if (newViewModel != null)
			{
				foreach (IViewModelBinder modelBinder in binders)
				{
					try
					{
						modelBinder.BindViewModelToView(view, newViewModel);
					}
					catch (Exception exp)
					{
						Logger.Error("Exception thrown while binding model", supplementalString, exp);
					}
				}
			}
		}

		private void ProcessOldModel(FrameworkElement view, object oldViewModel)
		{
			if (oldViewModel != null)
			{
				foreach (IViewModelBinder modelBinder in binders)
				{
					try
					{
						modelBinder.UnbindViewModelFromView(view, oldViewModel);
					}
					catch (Exception exp)
					{
						Logger.Error("Exception thrown while unbinding model", supplementalString, exp);
					}
				}
			}
		}
	}
}